package modelo.query;

import java.util.AbstractMap;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import modelo.Oferta;
import modelo.OrdenDeCompra;
import modelo.Producto;
import modelo.reglas.ISpecification;
import modelo.reglas.composite.AndSpecification;
import modelo.reglas.composite.BaseOperatorSpecification;
import modelo.reglas.composite.BaseOperatorSpecificationFactory;
import modelo.reglas.composite.NotSpecification;
import modelo.reglas.composite.OrSpecification;
import modelo.reglas.composite.inner.FieldValueSpecification;
import modelo.reglas.composite.inner.GroupSpecification;
import modelo.reglas.composite.inner.InnerSpecification;
import modelo.reglas.composite.inner.InnerSpecificationFactory;
import modelo.reglas.composite.inner.RangeSpecification;


public class QueryTranslator {

	private Map<BaseOperatorSpecification,Class> baseOperators;
	private Map<InnerSpecification,Class> innerOperators;
	
	public QueryTranslator() {
		baseOperators = new TreeMap<BaseOperatorSpecification, Class>();
		
		baseOperators.put(BaseOperatorSpecification.NOT, NotSpecification.class);
		baseOperators.put(BaseOperatorSpecification.AND, AndSpecification.class);
		baseOperators.put(BaseOperatorSpecification.OR, OrSpecification.class);
		
		innerOperators = new HashMap<InnerSpecification, Class>();
		innerOperators.put(InnerSpecification.EQUALS, FieldValueSpecification.class);
                innerOperators.put(InnerSpecification.DOTS, FieldValueSpecification.class);
		innerOperators.put(InnerSpecification.RANGE, RangeSpecification.class);
	}
	
	public GroupSpecification<Producto> translateQueryForProduct(String query,Oferta oferta) throws InvalidQueryException{
		List<ISpecification<Producto>> groupConditions = new ArrayList<ISpecification<Producto>>();
		
		String[] splittedGroupConditions;
                int min = -1;
                int max = -1;
                
                
                //@min@max@ query type
                if(query.indexOf("@") !=-1){
                    int init,middle,end;

                    init = query.indexOf("@");
                    middle = query.indexOf("@",init+1);
                    end = query.indexOf("@",middle+1);
                    
                    min = Integer.parseInt( query.substring(init+1,middle));
                    max = Integer.parseInt( query.substring(middle+1,end));
                    
                    query = query.substring(0,init);

                }
                
		
		if (query.indexOf("|") ==-1){
			splittedGroupConditions = new String[1];
			splittedGroupConditions[0] = query;
		}else{
			splittedGroupConditions= query.split("\\|");
		}
		
                
                
                
		for (String condition : splittedGroupConditions) {
			Map<Integer, String> breakedBasicOperators = breakBasicOperators(condition);
			
			groupConditions.add(translateToSpecification(breakedBasicOperators));
		}

		return new GroupSpecification<Producto>(oferta,min,max,groupConditions);
	}
        
        	public GroupSpecification<Producto> translateQueryForProduct(String query) throws InvalidQueryException{
		return translateQueryForProduct(query,null);
	}
	public GroupSpecification<Producto> translateQueryForProduct(Oferta oferta) throws InvalidQueryException{
		return translateQueryForProduct(oferta.getCondicionOferta(),oferta);
	}

        
        public GroupSpecification<OrdenDeCompra> translateQueryForSell(String query,Oferta oferta) throws InvalidQueryException{
		List<ISpecification<OrdenDeCompra>> groupConditions = new ArrayList<ISpecification<OrdenDeCompra>>();
		
		String[] splittedGroupConditions;
		
		if (query.indexOf("|") ==-1){
			splittedGroupConditions = new String[1];
			splittedGroupConditions[0] = query;
		}else{
			splittedGroupConditions= query.split("\\|");
		}
		
		for (String condition : splittedGroupConditions) {
			Map<Integer, String> breakedBasicOperators = breakBasicOperators(condition);
			
			groupConditions.add(translateToSpecification(breakedBasicOperators));
		}

		return new GroupSpecification<OrdenDeCompra>(oferta,-1,-1,groupConditions);
	}
	
        
        
	public GroupSpecification<OrdenDeCompra> translateQueryForSell(String query) throws InvalidQueryException{
		return translateQueryForSell(query,null);
	}
	public GroupSpecification<OrdenDeCompra> translateQueryForSell(Oferta oferta) throws InvalidQueryException{
		return translateQueryForSell(oferta.getCondicionOferta(),oferta);
	}

	private ISpecification translateToSpecification(
			Map<Integer, String> breakedBasicOperators) throws InvalidQueryException {
		
		AbstractMap.SimpleEntry<ISpecification,String> previousIteration= null; 
		for (int i = breakedBasicOperators.keySet().size()-1; i >= 0; i--) {
			Integer elemento = (Integer) breakedBasicOperators.keySet().toArray()[i];
			
			ISpecification specificationAnterior = procesarElementoBasico(breakedBasicOperators.get(elemento),previousIteration);
			
			previousIteration = new SimpleEntry<ISpecification, String>(specificationAnterior,breakedBasicOperators.get(elemento));
		}
		
		return previousIteration.getKey();
	}


	private ISpecification procesarElementoBasico(String operadorBasico, SimpleEntry<ISpecification, String> previousIteration) throws InvalidQueryException {
		
		//Todo operador basico tiene esta forma "asdasd , aslasd". La "," separa los distintos operadores inner
		BaseOperatorSpecification baseOperator = getBaseOperator(operadorBasico);
		
		return baseOperator.getSplitable() ? 
				procesarElementoBasicoConInnerSeparation(baseOperator, operadorBasico, previousIteration)
				: procesarElementoBasicoSinInnerSeparation(baseOperator, previousIteration);
	}

	private ISpecification procesarElementoBasicoConInnerSeparation(BaseOperatorSpecification baseOperator,String operadorBasico,SimpleEntry<ISpecification, String> previousIteration) throws InvalidQueryException {

		String operadorBasicoLimpio = operadorBasico.substring(baseOperator.getOperatorValue().length() + 1 , operadorBasico.length() - 1);

		//Si hubo iteracion anterior reemplazar en la actual
		if(previousIteration != null){
			operadorBasicoLimpio = operadorBasicoLimpio.replace(previousIteration.getValue(), "");
		}

		
		int indexOfDivisor = operadorBasicoLimpio.indexOf(',');
		//Bounderies
		if ( indexOfDivisor == -1){
			throw new InvalidQueryException();
		}
		
		String leftSide=operadorBasicoLimpio.substring(0,indexOfDivisor);
		String rightSide = operadorBasicoLimpio.substring(indexOfDivisor +1);
		
		//Bounderies
		if ( leftSide.isEmpty() && rightSide.isEmpty()){
			throw new InvalidQueryException();
		}
		
		ISpecification innerOpSpecificactionLeft = (leftSide.isEmpty() ?  previousIteration.getKey() : traducirInnerOperator(leftSide));
		ISpecification innerOpSpecificactionRight = (rightSide.isEmpty() ?  previousIteration.getKey() : traducirInnerOperator(rightSide));
		

		return BaseOperatorSpecificationFactory.build(baseOperator,innerOpSpecificactionLeft,innerOpSpecificactionRight);
	}

	
	private ISpecification procesarElementoBasicoSinInnerSeparation(BaseOperatorSpecification baseOperator,SimpleEntry<ISpecification, String> previousIteration) throws InvalidQueryException {
		return BaseOperatorSpecificationFactory.build(baseOperator,previousIteration.getKey(),null);
	}

	private ISpecification traducirInnerOperator(String innerOp) throws InvalidQueryException {
		InnerSpecification innerOperator = getInnerOperator(innerOp);
		
		String[] splitted = innerOp.split(innerOperator.getOperatorValue());
		
		return InnerSpecificationFactory.build(innerOperator,splitted[0],splitted[1]);
	}

	

	private InnerSpecification getInnerOperator(String innerOp) throws InvalidQueryException {
		for (InnerSpecification keyInnerOperator : innerOperators.keySet()) {
			if( innerOp.indexOf(keyInnerOperator.getOperatorValue()) != -1){
				return keyInnerOperator;
			}
		}
		
		throw new InvalidQueryException();
	}

	private BaseOperatorSpecification getBaseOperator(String operadorBasico) throws InvalidQueryException {
		String primerOperador = operadorBasico.substring(0, operadorBasico.indexOf('('));
		
		for (BaseOperatorSpecification keyBaseOperator : baseOperators.keySet()) {
			if( primerOperador.equalsIgnoreCase((keyBaseOperator.getOperatorValue()))){
				return keyBaseOperator;
			}
		}
		
		throw new InvalidQueryException();
	}

	private Map<Integer, String> breakBasicOperators(String query) {
		Map<Integer, BaseOperatorSpecification> positions = getBaseOperatorPositions(query);
		
		Map<Integer, String> lineasDeOperaciones = new TreeMap<Integer, String>();
		
		//Saco el ultimo
		for (int i = positions.keySet().size()-1; i >= 0; i--) {
			Integer primerElemento = (Integer) positions.keySet().toArray()[i];
		
			//Cuento ( hasta encontrar donde lo cierra y me quedo con ese renglon
			String operando = query.substring(primerElemento);
			
			int corchetesAbreCounter = 1;
			int offset = positions.get(primerElemento).getOperatorValue().length() + 1;
			while ( corchetesAbreCounter > 0){
				if ( operando.charAt(offset) == '('){
					corchetesAbreCounter++;
				}else if ( operando.charAt(offset) == ')'){
					corchetesAbreCounter--;
				}
				//Move in one caracter
				offset++;
			}
			
			lineasDeOperaciones.put(i, query.substring(primerElemento, primerElemento + offset));
		}
		
		return lineasDeOperaciones;
	}

	private Map<Integer, BaseOperatorSpecification> getBaseOperatorPositions(
			String query) {
		Map<Integer, BaseOperatorSpecification> positions = new TreeMap<Integer, BaseOperatorSpecification>();
		
		for (BaseOperatorSpecification keyBaseOperator : baseOperators.keySet()) {
			if( query.indexOf(keyBaseOperator.getOperatorValue()) != -1){
				
				int lastIndex = 0;
				do{
					lastIndex = query.indexOf(keyBaseOperator.getOperatorValue(),lastIndex);
					positions.put(query.indexOf(keyBaseOperator.getOperatorValue(),lastIndex), keyBaseOperator);
					lastIndex += keyBaseOperator.getOperatorValue().length();
				}while(query.indexOf(keyBaseOperator.getOperatorValue(), lastIndex) != -1 );
			}
		}
		return positions;
	}
}
